<template>
  <el-dialog
    v-model="fileExplorer"
    title="模型管理器"
    width="60%"
    append-to-body
    @close="chartEditStore.setFileExplorer(false)"
    destroy-on-close
  >
    <el-row :gutter="20" class="dialogBody">
      <el-col :span="6"
        ><div v-if="fileList.length" class="list" v-loading="dataLoading">
          <div v-for="(item, i) in fileList" :key="item.id" class="list-item">
            <div class="box">
              <img :src="item.imageUrl" class="img" alt="" />
              <div class="text">{{ item.name }}</div>
            </div>
            <div class="btnBox">
              <el-button-group>
                <el-button plain @click="openTemp(item)">使用</el-button>
                <el-button type="primary" plain @click="editTemp(item)">编辑</el-button>
                <el-button type="danger" plain @click="delTemp(item.id)">删除</el-button>
              </el-button-group>
            </div>
          </div>
        </div>
        <el-empty v-else description="暂无数据" />
      </el-col>
      <el-col :span="18" class="formBox">
        <el-form :model="formBox" label-width="80px" :rules="rules" ref="formRef">
          <el-row :gutter="20">
            <el-col :span="18">
              <el-form-item label="文件名" prop="name"
                ><el-input v-model="formBox.name" placeholder="请输入文件名"
              /></el-form-item>
            </el-col>
            <el-col :span="6">
              <n-upload
                v-model:file-list="importUploadFileListRef"
                :show-file-list="false"
                :customRequest="importCustomRequest"
                @before-upload="importBeforeUpload"
              >
                <el-button type="primary"> 导入 </el-button>
              </n-upload>
            </el-col>
            <el-col :span="24">
              <el-form-item label="预览" prop="url">
                <div ref="previewWrapRef" class="preview-wrap">
                  <canvas
                    ref="canvasRef"
                    id="canvasDialog"
                    v-if="isShow"
                    width="400"
                    height="300"
                    style="border: 1px solid #3c3c3c"
                  />
                </div>
              </el-form-item>
            </el-col>
          </el-row>
          <el-form-item>
            <el-button type="primary" v-loading="loading" @click="submitForm()">
              {{ loading ? '上传中...' : '保存' }}
            </el-button>
          </el-form-item>
        </el-form>
      </el-col>
    </el-row>
    <template #footer>
      <div class="dialog-footer">
        <el-button @click="chartEditStore.setFileExplorer(false)"> 关闭 </el-button>
      </div>
    </template>
  </el-dialog>
</template>

<script lang="ts" setup>
import { ref, watch, onMounted, toRaw, computed, nextTick, reactive } from 'vue'
import { loadingFinish, setComponentPosition } from '@/utils'
import { UploadCustomRequestOptions } from 'naive-ui'
import { ElLoading } from 'element-plus'
import * as THREE from 'three'
import { ElMessage, ElMessageBox } from 'element-plus'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js'
import {
  storedFileUploadFile,
  resourceCreate,
  resourceDelete,
  resourceGetList,
  resourceUpdate
} from 'swagger-api/export-api/scada-config'
import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
import { createComponent } from '@/packages'
import { CreateComponentType, ChartFrameEnum } from '@/packages/index.d'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
const emit = defineEmits(['close'])
const chartEditStore = useChartEditStore()
const fileExplorer = computed(() => chartEditStore.getFileExplorer)
const importUploadFileListRef = ref()
const formRef = ref()
const canvasRef = ref()
const previewWrapRef = ref()
// 本地预览 blob URL
const localObjectUrl = ref<string | null>(null)
const isShow = ref(true)
const formBox = ref({
  name: '',
  imageUrl: '',
  url: '',
  id: undefined
})
const rules = reactive({
  name: [{ required: true, message: '请输入名称', trigger: 'change' }],
  url: [
    {
      required: true,
      message: '请上传文件',
      trigger: 'change'
    }
  ]
})
// 渲染循环管理
let rafId = 0
let scene: THREE.Scene | null = null
let camera: THREE.PerspectiveCamera | null = null
let controls: any = null
const stopLoop = () => {
  if (rafId) {
    cancelAnimationFrame(rafId)
    rafId = 0
  }
}
const startLoop = (renderer: THREE.WebGLRenderer, s: THREE.Scene, c: THREE.PerspectiveCamera) => {
  stopLoop()
  const loop = () => {
    rafId = requestAnimationFrame(loop)
    controls?.update?.()
    renderer.render(s, c)
  }
  loop()
}
// Loading 实例（局部覆盖在 canvas 上）
let loadingInst: any = null
const openLoading = async (text = '加载中...') => {
  await nextTick()
  const target = previewWrapRef.value || canvasRef.value
  if (!target) return
  try {
    loadingInst?.close?.()
  } catch {}
  loadingInst = ElLoading.service({
    target,
    text,
    background: 'rgba(0,0,0,0.35)'
  })
}
const closeLoading = () => {
  try {
    loadingInst?.close?.()
  } catch {}
  loadingInst = null
}
// 上传-前置
//@ts-ignore
const importBeforeUpload = ({ file }) => {
  importUploadFileListRef.value = []
  const obj = file.name.split('.')
  const type = obj[obj.length - 1]
  if (!['gltf', 'glb'].includes(type)) {
    window['$message'].warning('仅支持上传 【GLTF,GLB】 格式文件，请重新上传！')
    return false
  }
  return true
}
// 上传-导入
const loading = ref(false)
const importCustomRequest = async (options: UploadCustomRequestOptions) => {
  const { file } = options
  try {
    if (file.file) {
      // 1) 先用本地 blob URL 预览，避免下载等待
      try {
        if (localObjectUrl.value) URL.revokeObjectURL(localObjectUrl.value)
        const f = file.file as File
        const [s] = f.name.split('.')
        formBox.value.name = formBox.value.name ? formBox.value.name : s
        localObjectUrl.value = URL.createObjectURL(f)
        xr()
      } catch (e) {
        console.log(e)
      }
      // 2) 后台上传（可选），不阻塞预览
      ;(async () => {
        try {
          loading.value = true
          const res = await storedFileUploadFile({ file: file.file })
          // 这里不替换预览地址，保留本地预览即可
          // 如需使用线上地址，可取消下一行注释
          formBox.value.url = res.data
        } catch (e) {
          /* 忽略上传失败，不影响预览 */
        } finally {
          loading.value = false
        }
      })()
    } else {
      window['$message'].error('导入失败，请检查数据或联系管理员！')
    }
  } catch (e) {}
}
const absBase = new URL(import.meta.env.BASE_URL, window.location.origin).href
const decoder = new URL(`draco/gltf/`, absBase).href
const xr = async () => {
  if (!canvasRef.value || !localObjectUrl.value) return
  const canvas = canvasRef.value as HTMLCanvasElement
  // 复用 renderer，避免频繁创建导致上下文丢失
  let renderer = (canvas as any).__threeRenderer as THREE.WebGLRenderer | null
  if (!renderer) {
    const attrs: WebGLContextAttributes = {
      alpha: true,
      antialias: true,
      preserveDrawingBuffer: false,
      powerPreference: 'high-performance'
    }
    const gl = canvas.getContext('webgl2', attrs) || canvas.getContext('webgl', attrs)
    if (!gl) {
      window['$message']?.error('无法创建 WebGL 上下文')
      return
    }
    renderer = new THREE.WebGLRenderer({ canvas, context: gl })
    renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
    ;(canvas as any).__threeRenderer = renderer
    canvas.addEventListener(
      'webglcontextlost',
      e => {
        e.preventDefault()
        stopLoop()
        closeLoading()
      },
      { once: true }
    )
  }
  renderer.setSize(400, 300, false)

  // 停止上一轮并清空旧场景
  stopLoop()
  if (scene) {
    scene.traverse(obj => {
      const mesh: any = obj
      if (mesh?.isMesh) {
        mesh.geometry?.dispose?.()
        const materials = Array.isArray(mesh.material) ? mesh.material : [mesh.material]
        materials.forEach((m: any) => {
          if (!m) return
          Object.keys(m).forEach(k => {
            const v = m[k]
            if (v && v.isTexture) v.dispose?.()
          })
          m.dispose?.()
        })
      }
    })
    while (scene.children.length) scene.remove(scene.children[0])
  }
  scene = null
  camera = null
  controls = null
  renderer.setRenderTarget(null)
  renderer.clear(true, true, true)

  // 场景相机与光源
  scene = new THREE.Scene()
  camera = new THREE.PerspectiveCamera(45, 400 / 300, 0.1, 1000)
  camera.position.set(0, 0, 5)
  camera.lookAt(0, 0, 0)
  scene.add(new THREE.AmbientLight(0xffffff, 1))
  const dl = new THREE.DirectionalLight(0xffffff, 0.8)
  dl.position.set(5, 10, 7.5)
  scene.add(dl)

  // 控制器（懒加载）
  controls = new OrbitControls(camera, canvas)
  controls.enableDamping = true
  const loader = new GLTFLoader()
  try {
    const draco = new DRACOLoader()
    draco.setDecoderPath(decoder)
    draco.setDecoderConfig({ type: 'wasm' })
    loader.setDRACOLoader(draco)
  } catch {}

  // 打开局部 Loading
  openLoading('模型加载中 0%')
  loader.load(
    localObjectUrl.value,
    gltf => {
      const model = gltf.scene || gltf.scenes?.[0]
      if (!model) {
        closeLoading()
        window['$message']?.warning('模型为空')
        return
      }
      const box = new THREE.Box3().setFromObject(model)
      const center = box.getCenter(new THREE.Vector3())
      model.position.sub(center)
      const size = box.getSize(new THREE.Vector3())
      const maxAxis = Math.max(size.x, size.y, size.z) || 1
      const fov = camera!.fov * (Math.PI / 180)
      const dist = maxAxis / 2 / Math.tan(fov / 2) + 1.5
      camera!.position.set(0, 0, dist)
      camera!.near = Math.max(dist - maxAxis * 10, 0.01)
      camera!.far = dist + maxAxis * 20
      camera!.updateProjectionMatrix()
      controls.target.set(0, 0, 0)
      controls.update()

      model.traverse((o: any) => {
        if (o.isMesh && o.material) {
          o.material.side = THREE.DoubleSide
        }
      })
      scene!.add(model)
      closeLoading()
      startLoop(renderer!, scene!, camera!)
      try {
        //生成图片
        if (renderer) {
          renderer.preserveDrawingBuffer = true
          renderer.render(scene, camera)
          renderer.resetState()
        }
      } catch (e) {}
    },
    xhr => {
      if (xhr.total) {
        const p = Math.min(99, Math.round((xhr.loaded / xhr.total) * 100))
        if (loadingInst)
          loadingInst.setText ? loadingInst.setText(`模型加载中 ${p}%`) : (loadingInst.text = `模型加载中 ${p}%`)
      }
    },
    () => {
      closeLoading()
      window['$message']?.error('模型预览失败')
    }
  )
}
const fileList = ref([])
const dataLoading = ref(false)
//获取数据
const getData = async () => {
  try {
    dataLoading.value = true
    const res = await resourceGetList()
    fileList.value = res.data
  } catch (e) {
  } finally {
    dataLoading.value = false
  }
}
const clearBox = () => {
  stopLoop()
  closeLoading()
  const r: any = (canvasRef.value as any).__threeRenderer
  if (r) {
    try {
      r.dispose?.()
    } catch {}
    ;(canvasRef.value as any).__threeRenderer = null
  }
}
// 弹窗关闭时停止渲染并适当清理
watch(
  fileExplorer,
  v => {
    if (!v && canvasRef.value) {
      clearBox()
    }
    if (!v) {
      formRef.value?.resetFields()
      isShow.value = false
      setTimeout(() => {
        isShow.value = true
      }, 100)
    } else {
      getData()
    }
  },
  {
    deep: true,
    immediate: true
  }
)
//提交表单
const submitForm = async () => {
  const res = await formRef.value.validate()
  if (res) {
    loading.value = true
    try {
      // 确保渲染完成后再截图
      await new Promise(resolve => setTimeout(resolve, 100))
      
      const canvas = document.querySelector('#canvasDialog')
      if (!canvas) {
        return
      }
      
      // 确保截图时 preserveDrawingBuffer 为 true
      const renderer = (canvas as any).__threeRenderer
      if (renderer) {
        renderer.preserveDrawingBuffer = true
        // 强制重新渲染以确保画面正确
        if (scene && camera) {
          renderer.render(scene, camera)
        }
      }
      
      // 使用 Promise 包装 toBlob 以确保正确处理
      const blob = await new Promise<Blob | null>(resolve => {
        (canvas as HTMLCanvasElement).toBlob(blob => {
          resolve(blob)
        }, 'image/png')
      })
      
      if (blob) {
        // 创建一个 File 对象
        const file = new File([blob], 'canvas-image.png', { type: blob.type })
        // 你可以在这里上传文件或进行其他操作
        try {
          // 上传封面图片
          const { data } = await storedFileUploadFile({ file: file })
          formBox.value.imageUrl = data
          // 新建
          const res = await (formBox.value.id
            ? resourceUpdate(formBox.value.id, formBox.value)
            : resourceCreate(formBox.value))
          ElMessage({
            message: res.code == 0 ? '操作成功！' : '操作失败',
            type: res.code == 0 ? 'success' : 'error',
            duration: 2000
          })
          formRef.value.resetFields()
          clearBox()
          isShow.value = false
          setTimeout(() => {
            isShow.value = true
          }, 100)
          getData()
        } catch (e) {
          console.error('提交表单时出错:', e)
        }
      }
    } catch (e) {
      console.error('截图过程中出错:', e)
    } finally {
      // 恢复 preserveDrawingBuffer 为 false 以节省内存
      const canvas = document.querySelector('#canvasDialog')
      if (canvas) {
        const renderer = (canvas as any).__threeRenderer
        if (renderer) {
          setTimeout(() => {
            renderer.preserveDrawingBuffer = false
          }, 1000)
        }
      }
      loading.value = false
    }
  }
}
//打开
const openTemp = async (item: any) => {
  const { name, url, imageUrl } = item
  const dropData = {
    key: 'Mesh',
    chartKey: 'V' + name,
    conKey: 'VC' + name,
    title: name,
    category: 'Model',
    categoryName: '模型',
    package: 'Graphic',
    chartFrame: ChartFrameEnum.COMMON,
    image: 'Mesh.png'
  }
  let newComponent: CreateComponentType = await createComponent(dropData)
  newComponent.meshConfig = url
  newComponent.chartConfig.title = name
  newComponent.name = name
  newComponent.isGroup = false
  newComponent.option.position = [0, 0, 0]
  newComponent.option.scale = [1, 1, 1]
  newComponent.option.rotation = [0, 0, 0]
  setComponentPosition(newComponent, 0, 0)
  chartEditStore.addComponentList(newComponent, false, true)
  chartEditStore.setTargetSelectChart(newComponent.id)
  chartEditStore.setFileExplorer(false)
  loadingFinish()
}
const editTemp = (item: any) => {
  formBox.value = item
  localObjectUrl.value = item.url
  xr()
}
const delTemp = (id: any) => {
  ElMessageBox.confirm('是否删除此模型？', '消息', {
    confirmButtonText: 'OK',
    cancelButtonText: 'Cancel',
    type: 'warning'
  })
    .then(async () => {
      const { code } = await resourceDelete(id)
      ElMessage({
        message: code == 0 ? '操作成功！' : '操作失败',
        type: code == 0 ? 'success' : 'error',
        duration: 2000
      })
      code == 0 && getData()
    })
    .catch(() => {})
}
</script>

<style lang="scss" scoped>
.dialogBody {
  height: 500px;
  overflow-y: hidden;
}
.list {
  border: 1px solid #3c3c3c;
  border-width: 0 1px 1px 1px;
  height: 500px;
  overflow-y: auto;
  &-item {
    position: relative;
    display: flex;
    padding: 10px 0;
    cursor: pointer;
    border-top: 1px solid #3c3c3c;
    .btnBox {
      display: none;
      position: absolute;
      left: 0;
      top: 0;
      right: 0;
      bottom: 0;
      background-color: rgba(0, 0, 0, 0.5);
      align-items: center;
      justify-content: center;
    }
    &:hover {
      .btnBox {
        display: flex;
      }
    }
    .box {
      display: flex;
      &:hover {
        opacity: 0.6;
      }
      .text {
        overflow: hidden;
        word-break: break-all;
        text-overflow: ellipsis;
        display: -webkit-box;
        -webkit-line-clamp: 3;
        -webkit-box-orient: vertical;
        height: 72px;
      }
    }
    .img {
      width: 100px;
      height: 75px;
      margin-right: 10px;
      padding: 0 10px;
    }
  }
}
.formBox {
  height: 500px;
  overflow-y: auto;
}
.dialog-footer {
  text-align: right;
}
.preview-wrap {
  position: relative;
  display: inline-block;
}
</style>
